package com.xuecheng.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.xuecheng.base.model.PageParams;
import com.xuecheng.base.model.PageResult;
import com.xuecheng.search.dto.SearchCourseParamDto;
import com.xuecheng.search.dto.SearchPageResultDto;
import com.xuecheng.search.po.CourseIndex;
import com.xuecheng.search.service.CourseSearchService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 课程搜索service实现类
 */
@Slf4j
@Service
public class CourseSearchServiceImpl implements CourseSearchService {

    /*索引*/
    @Value("${elasticsearch.course.index}")
    private String courseIndexStore;

    /*文档字段*/
    @Value("${elasticsearch.course.source_fields}")
    private String sourceFields;

    /*ES客户端*/
    @Autowired
    RestHighLevelClient client;

    /**
     * 搜索课程列表
     *
     * @param pageParams        分页参数
     * @param courseSearchParam 搜索条件
     * @return 搜索结果分页DTO
     */
    @Override
    public SearchPageResultDto<CourseIndex> queryCoursePubIndex(PageParams pageParams, SearchCourseParamDto courseSearchParam) {
        // 搜索文档，创建搜索请求对象（GET /索引名称/_search）
        SearchRequest searchRequest = new SearchRequest(courseIndexStore);
        // 搜索源构造器（用于构建搜索请求的各个部分）
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        // source源字段过滤（无需分词的文档字段）
        String[] sourceFieldsArray = sourceFields.split(",");
        // 参数一：在搜索结果中包含的字段，参数二：需要排除的字段（无需排除任何字段）
        searchSourceBuilder.fetchSource(sourceFieldsArray, new String[]{});

        // 判断查询参数是否为空
        if (courseSearchParam == null) {
            courseSearchParam = new SearchCourseParamDto();
        }

        // 获取查询条件中的关键字
        String keywords = courseSearchParam.getKeywords();
        // 大分类
        String mt = courseSearchParam.getMt();
        // 小分类
        String st = courseSearchParam.getSt();
        // 课程等级（难度）
        String grade = courseSearchParam.getGrade();

        // 创建布尔查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        // 关键字搜索
        if (StringUtils.isNotEmpty(keywords)) {
            // 匹配关键字（多条件匹配）
            MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(
                    keywords, "name", "description");
            // 设置匹配占比（70%表示的是字符串的相似程度）
            multiMatchQueryBuilder.minimumShouldMatch("70%");
            // 提升name字段的Boost权重值
            multiMatchQueryBuilder.field("name", 10);
            // 添加must条件(必须，参与加分)
            boolQueryBuilder.must(multiMatchQueryBuilder);
        }

        // 添加filter条件(必须，不参与加分)
        if (StringUtils.isNotEmpty(mt)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("mtName", mt));
        }
        if (StringUtils.isNotEmpty(st)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("stName", st));
        }
        if (StringUtils.isNotEmpty(grade)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("grade", grade));
        }

        // 分页
        Long pageNo = pageParams.getPageNo();
        Long pageSize = pageParams.getPageSize();
        int start = (int) ((pageNo - 1) * pageSize);
        searchSourceBuilder.from(start);
        searchSourceBuilder.size(Math.toIntExact(pageSize));

        // 布尔查询
        searchSourceBuilder.query(boolQueryBuilder);

        // 高亮设置
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<font class='eslight'>");
        highlightBuilder.postTags("</font>");
        // 设置高亮字段
        highlightBuilder.fields().add(new HighlightBuilder.Field("name"));
        searchSourceBuilder.highlighter(highlightBuilder);

        // 请求搜索
        searchRequest.source(searchSourceBuilder);

        // 聚合设置
        buildAggregation(searchRequest);

        // 搜索结果响应
        SearchResponse searchResponse;
        try {
            // 发送请求
            searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("课程搜索异常：{}", e.getMessage());
            // 返回空数据
            return new SearchPageResultDto<>(new ArrayList<>(), 0, 0, 0);
        }

        // 结果集处理
        SearchHits hits = searchResponse.getHits();
        SearchHit[] searchHits = hits.getHits();
        // 记录总数
        TotalHits totalHits = hits.getTotalHits();
        // 数据列表
        List<CourseIndex> list = new ArrayList<>();

        for (SearchHit hit : searchHits) {
            // 获取具体的数据
            String sourceAsString = hit.getSourceAsString();
            // 反序列化（JSON -> Java实体类对象）
            CourseIndex courseIndex = JSON.parseObject(sourceAsString, CourseIndex.class);

            // 课程id
            Long id = courseIndex.getId();
            // 课程名称
            String name = courseIndex.getName();
            // 取出高亮字段内容
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (highlightFields != null) {
                HighlightField nameField = highlightFields.get("name");
                if (nameField != null) {
                    Text[] fragments = nameField.getFragments();
                    StringBuffer stringBuffer = new StringBuffer();
                    for (Text str : fragments) {
                        stringBuffer.append(str.string());
                    }
                    name = stringBuffer.toString();

                }
            }
            courseIndex.setId(id);
            courseIndex.setName(name);

            list.add(courseIndex);

        }
        SearchPageResultDto<CourseIndex> pageResult = new SearchPageResultDto<>(list, totalHits.value, pageNo, pageSize);

        // 获取聚合结果
        List<String> mtList = getAggregation(searchResponse.getAggregations(), "mtAgg");
        List<String> stList = getAggregation(searchResponse.getAggregations(), "stAgg");

        pageResult.setMtList(mtList);
        pageResult.setStList(stList);

        return pageResult;
    }


    /**
     * 构建聚合查询
     * 创建一个terms聚合，名为'mtAgg'，针对的字段是'mtName'，并且桶的大小为100。
     * 这意味着，这个聚合将返回'mtName'字段的前100个不同值及其相关的文档数量。
     *
     * @param request 搜索请求对象
     */
    private void buildAggregation(SearchRequest request) {
        request.source().aggregation(AggregationBuilders
                .terms("mtAgg")
                .field("mtName")
                .size(100)
        );
        request.source().aggregation(AggregationBuilders
                .terms("stAgg")
                .field("stName")
                .size(100)
        );

    }

    /**
     * 获取聚合查询的结果
     * 搜索界面上显示的一级分类、二级分类来源于搜索结果，
     * 使用聚合搜索实现找到搜索结果中的一级分类、二级分类
     *
     * @param aggregations 聚合查询对象
     * @param aggName      聚合名称
     * @return List<String>
     */
    private List<String> getAggregation(Aggregations aggregations, String aggName) {
        // 根据聚合名称获取聚合结果
        Terms brandTerms = aggregations.get(aggName);
        // 获取buckets
        List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
        // 遍历
        List<String> brandList = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            // 获取key
            String key = bucket.getKeyAsString();
            brandList.add(key);
        }
        return brandList;
    }
}
